﻿// <copyright file="MockGameProvider.cs" company="Ecole Normale Supérieure">
// Released under GPLv2
// </copyright>
// <summary>Contains the mock game provider</summary>

namespace Sqwarea.Persistence.Mock
{
    using System;
    using System.Collections.Generic;
    using System.Drawing;
    using System.Linq;
    using Sqwarea.Persistence.Exceptions;

    /// <summary>
    /// Mock for GameProvider
    /// </summary>
    public class MockGameProvider : IGameProvider
    {
        /// <summary>
        /// The set of squares indexed by position (the map).
        /// </summary>
        private static Dictionary<Point, Square> _squares = new Dictionary<Point, Square>();

        /// <summary>
        /// The set of attacks.
        /// </summary>
        private static List<Attack> _attacks = new List<Attack>();

        /// <summary>
        /// The dictionnary of kings (indexed by king id).
        /// </summary>
        private static Dictionary<long, King> _kings = new Dictionary<long, King>();

        /// <summary>
        /// Last used king Id.
        /// Automatically initialized by 0.
        /// </summary>
        private static long _lastKingId;

        // Getting functions

        /// <summary>
        /// Gets the next King Id.
        /// WARNING : Mock implementation is very different from real one.
        /// This Mock implementation just use an internal variable to store last king id;
        /// </summary>
        /// <returns>Next king Id</returns>
        public long GetNextKingId()
        {
            _lastKingId++;
            return _lastKingId;
        }

        /// <summary>
        /// Get the object king corresponding to the kingId
        /// </summary>
        /// <param name="kingId">Id of the required king</param>
        /// <returns>See summary</returns>
        public King GetKingById(long kingId)
        {
            if (! _kings.ContainsKey(kingId))
            {
                throw new PersistenceKingNotFoundException(kingId, "King not found."); 
            }

            return (King) _kings[kingId].Clone();
        }

        /// <summary>
        /// Get the object square corresponding to the position position
        /// </summary>
        /// <param name="position">Position required</param>
        /// <returns>See summary</returns>
        public Square GetSquareByPosition(Point position)
        {
            if (_squares.ContainsKey(position))
            {
                return (Square) _squares[position].Clone();
            }
            else
            {
                return new Square(position);
            }
        }

        /// <summary>
        /// Get all the squares in a given rectangle
        /// </summary>
        /// <param name="rectangle">Rectangle delimiting the area required</param>
        /// <returns>See summary</returns>
        public Square[,] GetSquaresInRectangle(Rectangle rectangle)
        {
            if (rectangle.Height <= 0 || rectangle.Width <= 0)
            {
                throw new ArgumentOutOfRangeException("rectangle");
            }

            var squares = new Square[rectangle.Height, rectangle.Width];

            for (int i = 0; i < rectangle.Height; i++)
            {
                for (int j = 0; j < rectangle.Width; j++)
                {
                    squares[i, j] = GetSquareByPosition(new Point(j + rectangle.X, i + rectangle.Y));
                }
            }

            return squares;
        }

        /// <summary>
        /// Get the square at point and the surrondings ones, which has the same partition key
        /// </summary>
        /// <param name="point">Point that must be retrieved</param>
        /// <returns>An IEnumerable containing all square surronding point</returns>
        public IEnumerable<Square> GetSquaresInPartition(Point point)
        {
            var partitionKey = this.GetPartitionKey(point);

            var squares = from square in _squares
                          where partitionKey == this.GetPartitionKey(square.Value.Position)
                          select (Square) square.Value.Clone();

            return squares;
        }

        /// <summary>
        /// Gets all squares of the map
        /// Only used by ConnexityTotalChecker.
        /// NEVER USED IT FOR OTHER THING !
        /// </summary>
        /// <returns>All squares of the map</returns>
        public IEnumerable<Square> GetAllSquares()
        {
            return from square in _squares
                   select (Square) square.Value.Clone();
        }

        /// <summary>
        /// Gets the incoming attacks to kingId user (i.e. attacks against this king's squares).
        /// </summary>
        /// <param name="kingId">Id of the required king</param>
        /// <returns>See summary</returns>
        public IEnumerable<Attack> GetIncomingAttacks(long kingId)
        {
            var attacks = from attack in MockGameProvider._attacks
                          where GetSquareByPosition(attack.Destination).KingId == kingId 
                                && attack.ArrivalDate.CompareTo(DateTimeOffset.UtcNow) > 0
                          select attack;

            return attacks;
        }

        /// <summary>
        /// Gets the ongoing attacks og kingId user (i.e. attacks sended by this king).
        /// </summary>
        /// <param name="kingId">Id of the required king</param>
        /// <returns>See summary</returns>
        public IEnumerable<Attack> GetOngoingAttacks(long kingId)
        {
            var attacks = from attack in MockGameProvider._attacks
                          where attack.SenderId == kingId && attack.ArrivalDate.CompareTo(DateTimeOffset.UtcNow) > 0
                          select attack;

            return attacks;
        }

        // Setting functions

        /// <summary>
        /// Update the information on the square defined by its internal position
        /// </summary>
        /// <param name="square">Square to update (containing position)</param>
        public void UpdateSquare(Square square)
        {
            _squares[square.Position] = (Square) square.Clone();
        }

        /// <summary>
        /// Atomic update of a Square
        /// </summary>
        /// <param name="position">The position of the square to update</param>
        /// <param name="updateFunction">Function that must be called on the square.</param>
        /// <returns>the updated Square</returns>
        public Square UpdateSquare(Point position, Func<Square, Square> updateFunction)
        {
            Square result;

            if (_squares.ContainsKey(position))
            {
                result = updateFunction(_squares[position]);
            }
            else
            {
                result = updateFunction(new Square(position));
            }

            _squares[position] = (Square) result.Clone();

            return result;
        }

        /// <summary>
        /// Update the information on the squares given in the IEnumerable
        /// </summary>
        /// <param name="squares">Squares to update (containing position)</param>
        public void UpdateSquares(IEnumerable<Square> squares)
        {
            foreach (var square in squares)
            {
                UpdateSquare(square);
            }
        }

        /// <summary>
        /// Update the information on an existing king (defined by its id) or create a new king if the id is
        /// not referenced
        /// </summary>
        /// <param name="king">King to create or update (containing id)</param>
        public void UpdateKing(King king)
        {
            _kings[king.Id] = (King) king.Clone();
        }

        /// <summary>
        /// Atomic update of a King
        /// </summary>
        /// <param name="kingId">Id of the king to create or update</param>
        /// <param name="updateFunction">Function that must be called on the king</param>
        /// <returns>the updated King</returns>
        public King UpdateKing(long kingId, Func<King, King> updateFunction)
        {
            King result;

            if (_kings.ContainsKey(kingId))
            {
                result = updateFunction(_kings[kingId]);
            }
            else
            {
                throw new PersistenceKingNotFoundException(kingId, "UpdateKing");
            }

            _kings[kingId] = (King) result.Clone();

            return result;
        }

        /// <summary>
        /// Returns an IEnumerable that contains all kings. For everyday top kings sorting
        /// </summary>
        /// <returns>An object from which we can get all kings.</returns>
        public IEnumerable<King> GetAllKings()
        {
            return MockGameProvider._kings.Select(king => (King) king.Value.Clone());
        }

        /// <summary>
        /// Create a new attack
        /// </summary>
        /// <param name="attack">Attack to create</param>
        public void NewAttack(Attack attack)
        {
            MockGameProvider._attacks.Add(attack);
        }

        /// <summary>
        /// Allow a little bit of cleaning from times to times.
        /// </summary>
        /// <param name="kingIds">King that must be "cleaned"</param>
        public void ClearElapsedAttacks(IEnumerable<long> kingIds)
        {
            MockGameProvider._attacks =
                (from attack in MockGameProvider._attacks
                 where (kingIds.Contains(attack.SenderId) && attack.ArrivalDate.CompareTo(DateTimeOffset.UtcNow) < 0)
                 select attack).ToList();
        }

        // Clear all

        /// <summary>
        /// Clear all data (used only for tests purpose)
        /// </summary>
        internal void ClearAll()
        {
            _squares.Clear();
            _attacks.Clear();
            _kings.Clear();
        }
    }
}
